﻿using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using K9Nano.LayIM.Events;
using K9Nano.LayIM.Models;

namespace K9Nano.LayIM.Internal
{
    internal class MemoryChatAppOnlineStaffCache : IChatAppOnlineStaffCache
    {
        private readonly ConcurrentDictionary<string, AppOnlineStaff> _apps;
        private readonly ConcurrentDictionary<string, OnlineNotification> _rooms;
        private readonly ConcurrentDictionary<string, ConcurrentQueue<LayIMChatRoom>> _appQueue;
        
        public MemoryChatAppOnlineStaffCache()
        {
            _apps = new ConcurrentDictionary<string, AppOnlineStaff>();
            _appQueue = new ConcurrentDictionary<string, ConcurrentQueue<LayIMChatRoom>>();
            _rooms = new ConcurrentDictionary<string, OnlineNotification>();

            // TODO Load all rooms
        }

        public ValueTask OnlineAsync(OnlineNotification staff)
        {
            _apps.AddOrUpdate(staff.App,
                k => new AppOnlineStaff(staff),
                (k, list) => list.Add(staff));

            return new ValueTask();
        }

        public ValueTask OfflineAsync(OfflineNotification staff)
        {
            _apps.AddOrUpdate(staff.App,
                k => new AppOnlineStaff(),
                (k, list) => list.Remove(staff));

            return new ValueTask();
        }

        public OnlineNotification Next(LayIMChatRoom room)
        {
            if (_rooms.TryGetValue(room.Id, out var online))
            {
                return online;
            }

            if (_apps.TryGetValue(room.App, out var staffs))
            {
                online = staffs.Next();

                _rooms.TryAdd(room.Id, online);

                return online;
            }

            return null;
        }

        public void Enqueue(LayIMChatRoom room)
        {
            _appQueue.AddOrUpdate(room.App,
                k =>
                {
                    var queue = new ConcurrentQueue<LayIMChatRoom>();
                    queue.Enqueue(room);
                    return queue;
                }, (k, q) =>
                {
                    q.Enqueue(room);
                    return q;
                });
        }

        public bool TryDequeue(string app, out LayIMChatRoom room)
        {
            if (_appQueue.TryGetValue(app, out var queue)
                && queue.TryDequeue(out room))
            {
                return true;
            }

            room = null;
            return false;
        }

        private class AppOnlineStaff
        {
            private int _index;

            private readonly IList<OnlineNotification> _staffs;

            public AppOnlineStaff()
            {
                _index = -1;
                _staffs = new List<OnlineNotification>();
            }

            public AppOnlineStaff(OnlineNotification staff)
            {
                _index = -1;
                _staffs = new List<OnlineNotification> { staff };
            }

            public AppOnlineStaff Add(OnlineNotification staff)
            {
                foreach (var user in _staffs)
                {
                    if (user.UserId == staff.UserId)
                    {
                        user.ConnectionId = staff.ConnectionId;
                        return this;
                    }
                }

                _staffs.Add(staff);

                return this;
            }

            public AppOnlineStaff Remove(OfflineNotification staff)
            {
                for (var i = 0; i < _staffs.Count; i++)
                {
                    var user = _staffs[i];
                    if (user.UserId == staff.UserId)
                    {
                        user.ConnectionId = staff.ConnectionId;
                        _staffs.RemoveAt(i);
                        return this;
                    }
                }

                return this;
            }

            public OnlineNotification Next()
            {
                if (_staffs.Count == 0) return null;

                var i = _index;
                Interlocked.Increment(ref i);
                _index = i;
                return _staffs[_index % _staffs.Count];
            }
        }
    }
}